/**
 * @file eefs.cpp
 * @detail  EE block map
 *                          +----------------------------------+
 *                          |eeFat                                                     |
 *                          +----------------------------------+
 *                          |link|                                                      |
 *                          +----------------------------------+
 *                          |link|                                                      |
 *                          +----------------------------------+
 *
 * @author    - Adela 
 *            - Robert Zhang <armner@gmail.com>
 *            - 
 *
 */

#include "stdlib.h"
#include "string.h"
#include "eefs.h"

#define EEFS_VERSION    (0x10)
#define EE_FIRST_BLOCK ( (sizeof(eeFat)+EE_BLOCK_SIZE)/EE_BLOCK_SIZE)
#define RLE_ESC (0x97)

EeFs::EeFs(Driver *dvr)
{
    driver =dvr;

    driver->open(driver);

    driver->read(driver, &eeFat, sizeof(eeFat), 0);
}

EeFs::~EeFs()
{
    driver->close(driver);
}

void EeFs::setFreeList( EeBlockId freeList)
{
    eeFat.freeListHead =freeList;
    driver->write(driver, &freeList, sizeof(freeList), (OFFSET)&((EeFat*)0)->freeListHead);
}

void EeFs::setFileList(uint16_t fileId, EeBlockId startBlk)
{
    eeFat.files[fileId].listHead =startBlk;
    driver->write(driver, &startBlk, sizeof(startBlk), (OFFSET)&((EeFat*)0)->files[fileId].listHead);
}

void EeFs::setLink(EeBlockId block, EeBlockId next_block)
{
    driver->write(driver, &next_block, sizeof(EeBlockId), block*EE_BLOCK_SIZE);
}

EeBlockId EeFs::getLink(EeBlockId block)
{
    EeBlockId next_block;
    driver->read( driver, &next_block, sizeof(next_block), block *EE_BLOCK_SIZE);

    return next_block;
}

void EeFs::blockFree(EeBlockId block)
{
    ///free one or more blocks
    EeBlockId i = block;
    while( getLink(i)) i = getLink(i);

    setLink(i, eeFat.freeListHead);

    setFreeList( block);
}

EeBlockId EeFs::blockAlloc()
{
    EeBlockId blk=eeFat.freeListHead;

    if(blk){
        setFreeList(getLink(blk));
        setLink(blk,0);
    }

    return blk;
}


/// all files.listHead =0;
int EeFs::format()
{
    EeBlockId i;
    memset(&eeFat, 0, sizeof(eeFat) );
    eeFat.version =EEFS_VERSION;
    eeFat.mySize =sizeof(eeFat);
    eeFat.freeListHead =0;
    eeFat.bs =EE_BLOCK_SIZE;
    
    for( i = EE_FIRST_BLOCK; i < EE_MAX_BLOCKS; i++) 
    {
        setLink(i,i+1);
    }

    setLink(EE_MAX_BLOCKS-1, 0);
    eeFat.freeListHead = EE_FIRST_BLOCK;

    driver->write( driver, &eeFat, sizeof(eeFat), 0);

    return 0;
}

int EeFs::check()
{
    EeBlockId blk ;
    int8_t ret=0;
    
    uint8_t blockMap[EE_MAX_BLOCKS>>3];
#define BLOCK_IN_USE(x)   (blockMap[x>>3] & (1<<(x&7) ))

    if( (eeFat.version !=EEFS_VERSION) || (eeFat.mySize != sizeof(eeFat) ) ) return EE_ERR_FAIL;

    memset(blockMap, 0, sizeof(blockMap));

    for(uint8_t i = 0; i <= EE_MAX_FILES; i++)
    {
        EeBlockId prevBlk = 0;
        EeBlockId *pStartBlock;
        
        pStartBlock = (i==EE_MAX_FILES) ? &eeFat.freeListHead : &eeFat.files[i].listHead;
        blk =*pStartBlock;
            
        while(blk)
        {
            if(blk >=EE_MAX_BLOCKS) return EE_ERR_FAIL;
            else if( (   blk <  EE_FIRST_BLOCK ) //goto err_1; //bad blk index
                || (BLOCK_IN_USE(blk)     )//goto err_3; //blk double usage
            ){
                /// add termination
                if(prevBlk)
                {
                    setLink(prevBlk,0);
                }

                /// zero-sized
                else
                {
                    setFileList(i, 0);
                }

                blk=0; //abort
            }

            else
            {
                blockMap[blk>>3] |= 1<<(blk&7);
                prevBlk   = blk;
                blk       = getLink(blk);
            }
        }
    }

    for(blk = EE_FIRST_BLOCK; blk < EE_MAX_BLOCKS; blk++)
    {
        if(! BLOCK_IN_USE(blk))
        {
             setLink(blk, eeFat.freeListHead);
             setFreeList(blk);
        }
    }

    return ret;
}

int EeFs::open(EeFile *fd, uint8_t fileId, uint8_t mode)
{
    int ret;
    fd->m_mode =mode;
    fd->m_fileId =fileId;

    if(mode & EE_MODE_READ)
    {

        if( fd->m_fileId || fd->m_fileId< EE_MAX_FILES)
        {
            if(eeFat.files[fd->m_fileId].listHead)
            {
                fd->m_currBlk =eeFat.files[fd->m_fileId].listHead;
                fd->m_offs =0;
                fd->m_pos =0;
                fd->m_size =eeFat.files[fd->m_fileId].size;
                ret =EE_OK;
            }
            else ret =EE_ERR_FAIL;
        }
        else
            ret = EE_ERR_FAIL;
    }
    else if( mode & EE_MODE_WRITE)
    {
        if(fd->m_fileId || fd->m_fileId< EE_MAX_FILES)
        {
            if(eeFat.files[fd->m_fileId].listHead)
            {
                blockFree(eeFat.files[fd->m_fileId].listHead);
                setFileList(fd->m_fileId, 0);
            }
            fd->m_offs =0;
            fd->m_pos =0;
            fd->m_size =0;
            fd->m_currBlk =0;
        }
        else
        {
            ret =EE_ERR_FAIL;
        }
    }
    
    return ret;
}

int EeFs::close(EeFile *fd)
{
     if(fd->m_mode & EE_MODE_WRITE)
     {
        flush(fd);
    }

    return 0;
}

/**
 * @brief cached block read driver for EE
 * @param fd -file
 * @param buffer -dest buffer
 * @param len - buffer size
 * @param offset - ignored
 */
int EeFs::read(EeFile *fd, void *buf, unsigned int len, unsigned int offset)
{
    unsigned int t_len;
    char *buffer =(char *)buf;

    if( ! (fd->m_mode & EE_MODE_READ) ) return EE_ERR_FAIL;

    t_len= eeFat.files[fd->m_fileId].size - fd->m_pos;

    if(t_len < len) len = t_len;
    t_len =len;

    while(len)
    {
        if(!fd->m_currBlk)  return EE_ERR_FAIL;

        /// read a block of data instead of a byte
        if(fd->m_offs ==0)
        {
           driver->read(driver, fd->block_data, sizeof(fd->block_data), fd->m_currBlk*EE_BLOCK_SIZE +sizeof(EeBlockId) );
        }
       
        *buffer++ = fd->block_data[fd->m_offs++];
       
        if(fd->m_offs>=(EE_BLOCK_SIZE-sizeof(EeBlockId)) )
        {
           fd->m_offs=0;
           fd->m_currBlk=getLink(fd->m_currBlk);

        }

        len--;
        fd->m_pos ++;
    }
   
    return t_len;
}

int EeFs::write(EeFile *fd, void *buf, unsigned int len, unsigned int offset)
{
     unsigned int t_len;
     unsigned int len1;
     int m_err;
     char *buffer =(char *)buf;

    if(! (fd->m_mode & EE_MODE_WRITE) ) return EE_ERR_FAIL;

    len1 =len;
    while(len)
    {
        if(fd->m_offs ==0 )
        {
            if(fd->m_currBlk)
            {
                setLink(fd->m_currBlk, blockAlloc());
                fd->m_currBlk = getLink(fd->m_currBlk);
            }
            else
            {
                fd->m_currBlk =blockAlloc();
                eeFat.files[fd->m_fileId].listHead =fd->m_currBlk;
            }
 
            if( !fd->m_currBlk)
                return EE_ERR_FULL;
        }

        t_len = EE_BLOCK_SIZE -sizeof(EeBlockId) - fd->m_offs; if(t_len>len) t_len=len;

        //write to cache
        memcpy(fd->block_data + fd->m_offs, buffer, t_len);
        buffer   +=t_len;
        fd->m_offs +=t_len;

        if(fd->m_offs >=EE_BLOCK_SIZE-sizeof(EeBlockId) )
        {
            driver->write(driver, fd->block_data, sizeof(fd->block_data), fd->m_currBlk *EE_BLOCK_SIZE + sizeof(EeBlockId) );
            fd->m_offs=0;
        }
        len   -=t_len;
        fd->m_pos += t_len;
   }


    //write fat
    //eeFat.files[fd->m_fileId].size =fd->m_pos;
    //driver->write( driver, &eeFat.files[fd->m_fileId], sizeof(eeFat.files[0]), (OFFSET)&((EeFat*)0)->files[fd->m_fileId] );
   return   len1;
}

int EeFs::flush(EeFile *fd)
{
    if(fd->m_mode & EE_MODE_WRITE)
    {
    
        if(fd->m_offs && fd->m_currBlk)
        {
            driver->write(driver, fd->block_data, sizeof(fd->block_data), fd->m_currBlk*EE_BLOCK_SIZE +sizeof(EeBlockId) );
        }

        eeFat.files[fd->m_fileId].size =fd->m_pos;
        driver->write( driver, &eeFat.files[fd->m_fileId], sizeof(eeFat.files[0]), (OFFSET)&((EeFat*)0)->files[fd->m_fileId] );

    }

    return 0;
}

int EeFs::readRLE(EeFile *fd, void *buf, unsigned int len, unsigned int offset)
{
    unsigned int i;
    char fsm=0;
    unsigned char counter;
    char curr_char;
    char *buffer =(char *)buf;

    char ctrl;

    if( 1 != read(fd, &ctrl, 1,0) ) return EE_ERR_FAIL;

    for(i=0; i<len;)
    {
        switch(fsm)
        {
        case 0:
            if ( 1 !=read(fd, &curr_char, 1, 0) ) return EE_ERR_FAIL;
            if(curr_char ==ctrl)
            {
                fsm =1;
            }
            else
            {
                *buffer ++= curr_char;
                i++;
            }
            break;

        case 1:
            if( 1 !=read(fd, &curr_char, 1, 0)) return EE_ERR_FAIL;
            if(curr_char ==ctrl)
            {
                *buffer++ =curr_char;
                i++;
                fsm =0;
            }
            else
            {
                counter =curr_char;
                fsm =2;
            }
            break;

        case 2:
            if( 1 !=read(fd, &curr_char, 1, 0)) return EE_ERR_FAIL;

            counter =counter<(len -i)? counter: len-i;
            memset(buffer , curr_char, counter);
            buffer +=counter;
            i +=counter;
            fsm =0;
            break;

        }
    }

    return len;
}

/**
 * @brief RLE algorithm compression
 *           fsm
 *
 *
 *
 */
int EeFs::writeRLE(EeFile *fd, void *buf, unsigned int len, unsigned int offset)
{
    char fsm =0;
    char prev_char;
    char curr_char;
    char t_char;
    unsigned int counter;
    unsigned int i;

    char *buffer =(char *)buf;
    char *p_char =buffer;
    char ctrl=RLE_ESC;
    for(;;)
    {
       counter =0;
       for( i=0; i<len; i++)
       {
           if(*p_char ++ == ctrl) counter ++;
       }
       if( (counter <<4) > len) ctrl ++;
       else break;
    }

    write(fd, &ctrl, 1, 0);

    for( i=0; i<len; i++)
    {
        curr_char = *buffer ++;
        switch(fsm)
        {
            case 0: ///< *
                prev_char = curr_char;
                fsm =1;
                break;
                
            case 1: ///< A*
                if(prev_char ==curr_char)
                {
                    fsm =2;
                }
                else
                {
                    if(prev_char ==ctrl)
                        write(fd, &prev_char, 1,0);
                    write(fd, &prev_char, 1, 0);
                    prev_char =curr_char;
                    fsm =1;
                }
                break;
                
            case 2: ///< AA *
                if(prev_char ==curr_char)
                {
                    fsm =3;
                    counter =3;
                }
                else
                {
                    if(prev_char == ctrl)
                        write( fd, &prev_char, 1,0);
                    write(fd, &prev_char, 1, 0);
                    if(prev_char == ctrl)
                        write( fd, &prev_char, 1,0);
                    write(fd, &prev_char, 1, 0);
                    prev_char =curr_char;
                    fsm =1;
                }
                break;
           case 3: ///< AAA*
               if(prev_char ==curr_char)
               {
                   counter ++;
                   if(counter ==64)
                   {
                       t_char =ctrl;
                       write(fd, &t_char, 1, 0);
                       write(fd, &counter, 1,0);
                       write(fd, &prev_char, 1,0);

                       fsm =0;
                   }
               }
               else
               {
                   //write
                   t_char =ctrl;
                   write(fd, &t_char, 1,0);
                   write(fd, &counter, 1,0);
                   write(fd, &prev_char, 1,0);

                   fsm =1;
                   prev_char =curr_char;
               }
               break;
        }
    }

   switch(fsm)
   {
       case 1:
           if(prev_char ==ctrl)
               write(fd, &prev_char, 1,0);
           write(fd, &curr_char, 1, 0);
           break;
       case 2:
           if(prev_char ==ctrl)
               write(fd, &prev_char, 1,0);
           write(fd, &curr_char, 1,0);
           if(prev_char ==ctrl)
               write(fd, &prev_char, 1,0);
           write(fd, &curr_char, 1,0);
           break;
       case 3:
            t_char =ctrl;
            write(fd, &t_char, 1,0);
            write(fd, &counter, 1,0);
            write(fd, &prev_char, 1,0);
            break;            
    }

    return len;
}

int EeFs::rm(EeFile *fd)
{
    EeBlockId blk;

    blk= eeFat.files[fd->m_fileId].listHead;
    blockFree(blk);
    setFileList(fd->m_fileId, 0);

    return 0;
}

char EeFs::checkSum(void *buffer, int len)
{
    char *pchar;
    int i;
    int sum;

    pchar =(char*)buffer;
    for(i=0; i<len; i++)
    {
        sum =*pchar++;
        sum +=sum>>8;
    }

    return sum;
}




